<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>装饰器模式（Decorator Pattern）</title>
  <!-- 
  装饰器模式（Decorator Pattern）是一种结构型设计模式，它允许在不改变对象自身的情况下，动态地向对象添加新的功能。装饰器模式通过创建一个装饰类来包装原始类，从而实现对其功能的扩展。这个模式特别适用于需要给对象添加额外职责的场景。

  ### 装饰器模式的主要组成部分

  1. **组件接口（Component）**：定义一个对象接口，可以给这些对象动态添加职责。
  2. **具体组件（Concrete Component）**：实现组件接口的具体对象，定义了要装饰的对象。
  3. **装饰器（Decorator）**：持有一个组件对象的引用，并实现组件接口。装饰器类可以在调用组件对象的方法之前或之后添加额外的功能。
  4. **具体装饰器（Concrete Decorator）**：扩展装饰器类，添加具体的功能。

  ### 装饰器模式的优点

  1. **灵活性**：可以在运行时动态添加或修改对象的功能，而无需修改对象的代码。
  2. **单一职责原则**：装饰器可以将功能分离到不同的类中，每个装饰器负责特定的功能，符合单一职责原则。
  3. **可扩展性**：可以通过组合多个装饰器来扩展功能，增强了系统的可扩展性。

  ### 装饰器模式的缺点

  1. **复杂性**：装饰器模式可能会导致系统中出现大量的小类，增加了系统的复杂性。
  2. **调试困难**：由于装饰器的嵌套，可能会使得调试变得更加困难，尤其是在有多个装饰器的情况下。

  ### 示例代码

  以下是一个使用装饰器模式的简单示例，展示如何动态地为一个文本对象添加额外的功能。

  #### 1. 组件接口（Component）


  ```javascript
  class Coffee {
      cost() {
          return 5; // 基础咖啡的价格
      }
  }
  ``` 

  #### 2. 装饰器（Decorator）


  ```javascript
  class CoffeeDecorator {
      constructor(coffee) {
          this.coffee = coffee; // 持有一个咖啡对象
      }

      cost() {
          return this.coffee.cost(); // 调用原始咖啡的价格
      }
  }
  ``` 


  #### 3. 具体装饰器（Concrete Decorators）


  ```javascript
  class MilkDecorator extends CoffeeDecorator {
      cost() {
          return this.coffee.cost() + 2; // 加入牛奶的价格
      }
  }

  class SugarDecorator extends CoffeeDecorator {
      cost() {
          return this.coffee.cost() + 1; // 加入糖的价格
      }
  }
  ``` 


  #### 4. 使用示例


  ```javascript
  let myCoffee = new Coffee();
  console.log(myCoffee.cost()); // 输出: 5

  myCoffee = new MilkDecorator(myCoffee);
  console.log(myCoffee.cost()); // 输出: 7

  myCoffee = new SugarDecorator(myCoffee);
  console.log(myCoffee.cost()); // 输出: 8
  ``` 


  ### 总结

  装饰器模式是一种灵活且强大的设计模式，适用于需要动态添加对象功能的场景。通过装饰器，可以在不修改原始对象的情况下扩展其功能，遵循开闭原则（对扩展开放，对修改关闭）。在设计系统时，合理运用装饰器模式可以提高代码的可维护性和可扩展性。
  -->
</head>

<body>

  <script>
    // 装饰器模式（Decorator Pattern）允许向一个现有的对象添加新的功能，同时又不改变其结构。这种类型的设计模式属于结构型模式，它是作为现有的类的一个包装。
    // 这种模式创建了一个装饰类，用来包装原有的类，并在保持类方法签名完整性的前提下，提供了额外的功能。

    // 介绍
    // 意图：动态地给一个对象添加一些额外的职责。就增加功能来说，装饰器模式相比生成子类更为灵活。

    // 适用场景
    // 1、需要扩展一个类的功能，或给一个类添加附加职责。
    // 2、需要动态地给一个对象添加功能，这些功能可以再动态地撤销。
    // 3、需要增加由一些基本功能的排列组合而产生的非常大量的功能，从而使继承关系变的不现实。

    // 1. 定义一个基础类和装饰器类
    class Coffee {
      cost() {
        return 5; // 咖啡的价格
      }
    }

    class CoffeeDecorator {
      constructor(coffee) {
        this.coffee = coffee;
      }
    }

    class MilkDecorator extends CoffeeDecorator {
      cost() {
        return this.coffee.cost() + 2; // 加入牛奶的价格
      }
    }

    class SugarDecorator extends CoffeeDecorator {
      cost() {
        return this.coffee.cost() + 1; // 加入糖的价格
      }
    }

    const myCoffee = new Coffee();
    console.log(myCoffee.cost()); // 输出: 5
    const myCoffeeWithMilk = new MilkDecorator(myCoffee);
    console.log(myCoffeeWithMilk.cost()); // 输出: 7
    const myCoffeeWithMilkAndSugar = new SugarDecorator(myCoffeeWithMilk);
    console.log(myCoffeeWithMilkAndSugar.cost()); // 输出: 8

    // 2. 使用装饰器模式
    function mixins(...list) {
      return function (target) {
        Object.assign(target.prototype, ...list);
      };
    }

    const Foo = {
      foo() { alert('foo') }
    }

    @mixins(Foo)
    class MyClass {
    }

    const obj = new MyClass();
    obj.foo(); // 输出: foo

    // 3. 使用装饰器模式
    function readonly(target, name, descriptor) {
      // descriptor对象原来的值如下
      // {
      //   value: specifiedFunction,
      //   enumerable: false,
      //   configurable: true,
      //   writable: true
      // };
      descriptor.writable = false;
      return descriptor;
    }

    class Person {
      constructor() {
        this.name = '小明';
        this.age = 18;
      }

      @readonly
      name() {
        return this.name + " - " + this.age;
      }
    }

    const person = new Person();
    console.log(person.name()); // 输出: 小明 - 18
    person.name = '小红'; // 报错: Cannot assign to read only property 'name' of object '#<Person>'


  </script>

</body>

</html>